/*
 * Copyright Konstantin Triger <kostat@gmail.com> 
 * 
 * This file is part of Jaque - JAva QUEry library <http://code.google.com/p/jaque/>.
 * 
 * Jaque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jaque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jaque.expressions;

import java.io.*;
import java.util.*;

import org.objectweb.asm.*;

final class ExpressionClassVisitor implements ClassVisitor {

	private final ConstantExpression _me;
	private final String _method;
	private Expression _result;
	private Class<?> _type;
	private Class<?>[] _argTypes;

	Expression getResult() {
		return _result;
	}

	ConstantExpression getThis() {
		return _me;
	}

	void setResult(Expression result) {
		_result = result;
	}

	LambdaExpression<?> lambda() {
		Class<?> me = _me.getResultType();

		String name = "/" + me.getName().replace('.', '/') + ".class";

		InputStream s = me.getResourceAsStream(name);

		try {
			try {
				ClassReader reader = new ClassReader(s);
				reader.accept(this, ClassReader.SKIP_DEBUG
						| ClassReader.SKIP_FRAMES);
			} finally {
				s.close();
			}
		} catch (IOException ioe) {
			throw new RuntimeException(ioe);
		}

		ParameterExpression[] params = new ParameterExpression[_argTypes.length];
		for (int i = 0; i < params.length; i++)
			params[i] = Expression.parameter(_argTypes[i], i);

		Expression result = TypeConverter.convert(getResult(), _type);

		return Expression.lambda(_type, result, Collections
				.unmodifiableList(Arrays.asList(params)));
	}

	public ExpressionClassVisitor(Object me) {

		String method = "invoke";
		while (me instanceof LambdaWrapper) {
			LambdaWrapper wrapper = (LambdaWrapper) me;
			me = wrapper.getWrapped();
			method = wrapper.getMethod();
		}

		_me = Expression.constant(me, me.getClass());
		_method = method;
	}

	Class<?> getClass(Type t) {
		try {
			switch (t.getSort()) {
			case Type.BOOLEAN:
				return Boolean.TYPE;
			case Type.CHAR:
				return Character.TYPE;
			case Type.BYTE:
				return Byte.TYPE;
			case Type.SHORT:
				return Short.TYPE;
			case Type.INT:
				return Integer.TYPE;
			case Type.FLOAT:
				return Float.TYPE;
			case Type.LONG:
				return Long.TYPE;
			case Type.DOUBLE:
				return Double.TYPE;
			case Type.VOID:
				return Void.TYPE;
			}
			String cn = t.getInternalName();
			cn = cn != null ? cn.replace('/', '.') : t.getClassName();

			return Class.forName(cn, false, _me.getResultType()
					.getClassLoader());
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	// @Override
	public MethodVisitor visitMethod(int access, String name, String desc,
			String signature, String[] exceptions) {

		if ((access & Opcodes.ACC_SYNTHETIC) != 0)
			return null;

		if (!_method.equals(name))
			return null;

		Type ret = Type.getReturnType(desc);
		if (ret.getSort() == Type.VOID)
			throw ExpressionMethodVisitor.notLambda(Opcodes.RETURN);

		_type = getClass(ret);

		Type[] args = Type.getArgumentTypes(desc);
		Class<?>[] argTypes = new Class<?>[args.length];

		for (int i = 0; i < args.length; i++)
			argTypes[i] = getClass(args[i]);

		_argTypes = argTypes;

		return new ExpressionMethodVisitor(this, argTypes);
	}

	// @Override
	public void visit(int arg0, int arg1, String arg2, String arg3,
			String arg4, String[] arg5) {
	}

	// @Override
	public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) {
		return null;
	}

	// @Override
	public void visitAttribute(Attribute arg0) {
	}

	// @Override
	public void visitEnd() {
	}

	// @Override
	public FieldVisitor visitField(int arg0, String arg1, String arg2,
			String arg3, Object arg4) {
		return null;
	}

	// @Override
	public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
	}

	// @Override
	public void visitOuterClass(String arg0, String arg1, String arg2) {
	}

	// @Override
	public void visitSource(String arg0, String arg1) {
	}

}
